package com.hqyj.fj.javaSpringBoot.modules.account.service.impl;

import com.hqyj.fj.javaSpringBoot.modules.account.dao.RoleDao;
import com.hqyj.fj.javaSpringBoot.modules.account.dao.UserDao;
import com.hqyj.fj.javaSpringBoot.modules.account.dao.UserRoleDao;
import com.hqyj.fj.javaSpringBoot.modules.account.entity.User;
import com.hqyj.fj.javaSpringBoot.modules.account.entity.UserRole;
import com.hqyj.fj.javaSpringBoot.modules.common.entity.Result;
import com.hqyj.fj.javaSpringBoot.modules.common.entity.SearchBean;
import com.hqyj.fj.javaSpringBoot.modules.account.dao.UserRepository;
import com.hqyj.fj.javaSpringBoot.modules.account.service.UserService;
import com.hqyj.fj.javaSpringBoot.util.MD5Util;
import com.hqyj.fj.javaSpringBoot.util.RedisUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserRepository userRepository;

    @Autowired
    private RedisUtils redisUtils;


    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    UserDao userDao;

    @Autowired
    RoleDao roleDao;

    @Autowired
    private UserRoleDao userRoleDao;

    @Override
    @Transactional
    public Result<User> editUser(User user) {
        System.out.println(user.getUserName());
        user.setPassword( MD5Util.getMD5( user.getPassword()));
        System.out.println(user);
        userRepository.saveAndFlush(user);
        return new Result<User>(Result.ResultCode.SUCCESS.code,
                "Insert success.", user);

    }

    @Override
    @Transactional
    public Result<User> insertUser(User user) {
        List<User> users = Optional
                .ofNullable(userRepository.findByUserNameAndEmail(user.getEmail(), user.getUserName()))
                .orElse(Collections.emptyList());
        if (users.size() > 0) {
            return new Result<User>(Result.ResultCode.FAILED.code, "User Name or email is repeat.");
        }

        user.setCreateDate(LocalDateTime.now());
        user.setPassword(MD5Util.getMD5(user.getPassword()));
        userRepository.saveAndFlush(user);
        if (user.getRoles() != null) {
            user.getRoles().stream()
                    .forEach(item -> {userRoleDao.insertUserRole(new UserRole(user.getId(), item.getId()));});
        }
        return new Result<User>(Result.ResultCode.SUCCESS.code, "Insert success", user);
    }

    @Override
    @Transactional
    public Result<User> updateUser(User user) {

        List<User> users = Optional
                .ofNullable(userRepository.findByUserNameAndEmail(user.getEmail(), user.getUserName()))
                .orElse(Collections.emptyList());
        if (users.stream().filter(item -> item.getId() != user.getId()).count() > 0) {
            return new Result<User>(Result.ResultCode.FAILED.code, "User Name or email is repeat.");
        }

        userRepository.saveAndFlush(user);
        if (user.getRoles() != null && !user.getRoles().isEmpty()) {
          userRoleDao.deleteUserRoleByUserId(user.getId());
            user.getRoles().stream()
                    .forEach(item -> {userRoleDao.insertUserRole(new UserRole(user.getId(), item.getId()));});
        }

        return new Result<User>(Result.ResultCode.SUCCESS.code, "Update success", user);
    }

    @Override
    @Transactional
    public Result<Object> insertUsers(List<User> userList) {
       userList.stream().forEach(item ->{
            entityManager.persist(item);
        });
        entityManager.flush();
        entityManager.clear();
        return  new Result<Object>(Result.ResultCode.SUCCESS.code,"insert success");
    }

    @Override
    public Result<Object> deleteStudentById(int id) {
        userRepository.deleteById(id);
        userRoleDao.deleteUserRoleByUserId(id);
        return new Result<Object>(Result.ResultCode.SUCCESS.code,
                "Delete success.");
    }

    @Override
    public User getUserById(int id) {
        return userDao.getUserById(id);


//        return userRepository.getUserById(id);
}

    @Override
    public List<User> getByUserNameAndEmail(String userName, String email) {
        return userRepository.findByUserNameAndEmail(userName,email);
    }

    @Override
    public Result<User> login(User user) {

        int loginFailMaxCount = 5;
        // 从数据库获取 错误登录次数
        String key = String.format("failed_login_count_%s", user.getUserName());
        int loginFailCount = redisUtils.get(key) == null ? 0 : (Integer)redisUtils.get(key);
        // 如果错误登录次数 > Max，直接返回错误信息
        if (loginFailCount >= loginFailMaxCount) {
            return new Result<User>(Result.ResultCode.FAILED.code,
                    String.format("该账户连续登录错误 %s 次，账户锁定 30 s.", loginFailCount));
        }
        // 获得 subject
        Subject subject = SecurityUtils.getSubject();

        // 包装令牌
        UsernamePasswordToken token = new UsernamePasswordToken(
                user.getUserName(),
                MD5Util.getMD5(user.getPassword()));
        token.setRememberMe(user.isRememberMe());

        try {
            // 调用 subject login 方法，进行身份验证
            subject.login(token);
            // 调用 subjectcheck roles 方法，进行资源授权
            subject.checkRoles();
            // 处理 session
            subject.getSession().setAttribute("user", subject.getPrincipal());

            //登录成功，将错误次数归 0
            redisUtils.set(key, 0);

        } catch (Exception e) {
            // redis 里错误的登录次数 +1
            loginFailCount += 1;
            redisUtils.set(key, loginFailCount);
            // 如果错误登录次数 + 1 == max， redis 设置该 key 的过期时间
            if (loginFailCount < loginFailMaxCount) {
                return new Result<User>(Result.ResultCode.FAILED.code,
                        String.format("登录错误，还剩 %s 次", (loginFailMaxCount - loginFailCount)));
            } else {
                // 返回错误消息
                redisUtils.expire(key, 30);
                return new Result<User>(Result.ResultCode.FAILED.code,
                        String.format("该账户连续登录错误 %s 次，账户锁定 30 s.", loginFailCount));
            }
        }
        // 返回 result
        return new Result<User>(Result.ResultCode.SUCCESS.code, "Success",
                (User) subject.getPrincipal());
    }

    @Override
    public void logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
    }

    @Override
    public User getByUserNameAndPassword(String userName, String password) {
        return userRepository.findFirstByUserNameAndPassword(userName,password);
    }

    @Override
    public Page<User> getUserBySearchBean(SearchBean searchBean) {
        Sort.Direction direction = StringUtils.isBlank(searchBean.getDirection()) ||
                searchBean.getDirection().toLowerCase().equals("asc") ?
                Sort.Direction.ASC : Sort.Direction.DESC;
        Sort sort = Sort.by(direction, searchBean.getOrderBy());
        Pageable Pageable = PageRequest.of(
                searchBean.getCurrentPage() - 1,
                searchBean.getPageSize(),
                sort);

       User user = new User();
       user.setEmail(searchBean.getKeyword());
        ExampleMatcher matcher = ExampleMatcher
                .matchingAny()
                .withMatcher("userName", match -> match.contains())
                .withMatcher("email", match -> match.contains());
        Example example = Example.of(user, matcher);

        return  userRepository.findAll(example, Pageable);
    }

    @Override
    public User getUserByUserName(String userName) {
        List<User> users = Optional
                .ofNullable(userRepository.findUserByUserName(userName))
                .orElse(Collections.emptyList());
        return users.isEmpty() ? null : users.get(0);
    }
}
